﻿<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

	<head>
		<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
		<title>Feature Management</title>
		<link type="text/css" rel="stylesheet" href="bootstrap.min.css" />
	</head>

	<body>

		<div class="document-contents">

			<h3>Introduction</h3>
			<p>Most <strong>SaaS</strong> (multi-tenant) applications have <strong>editions
				</strong>(packages) those have different <strong>features</strong>. Thus, they 
	can provide different <strong>price and feature options</strong> to thier 
	tenants (customers).</p>
			<p>ASP.NET Boilerplate provides a <strong>feature system</strong> to make 
	it easier. We can <strong>define</strong> features, <strong>check </strong>
	if a feature is <strong>enabled</strong> for a tenant and <strong>integrate
				</strong>feature system to other ASP.NET Boilerplate concepts (like 
			<a href="Authorization.html">authorization</a> 
	and <a href="Navigation.html">navigation</a>).</p>

			<div class="bs-callout bs-callout-warning">
				<h4>About IFeatureValueStore</h4>
				<p>Feature system uses <strong>IFeatureValueStore</strong> to 
		get values of features. While you can 
		implement it in your own way, it's fully implemented in <strong>module-zero</strong> 
		project. If it's not implemented, NullFeatureValueStore is used which 
		returns null for all features (Default feature values are used in this 
		case).</p>
			</div>

			<h3>Feature Types</h3>
			<p>There are two fundamental feature types.</p>
			<h4>Boolean Feature</h4>
			<p>Can be "true" or "false". This type of a feature can be <strong>enabled</strong> 
	or <strong>disabled</strong> (for an edition or for a tenant).</p>
			<h4>Value Feature</h4>
			<p>Can be an <strong>arbitrary value</strong>. While it's stored and 
	retrieved a string, numbers also can be stored as strings.</p>
			<p>For example, 
	our application may be a task management application and we may have a limit for 
	creating tasks in a month. Say that we have two different edition/package; one allows 
	creating 1,000 tasks per month, while other allows creating 5,000 
	tasks per month. So, this feature should be stored as value, not simply 
	true/false.</p>
			<h3>Defining Features</h3>
			<p>A feature should be defined before checking. A
				<a href="/Pages/Documents/Module-System">module</a> can define it's own 
	features by deriving from <strong>FeatureProvider</strong> class. Here, a 
	very simple feature provider that defines 3 features:</p>
			<pre lang="cs">public class AppFeatureProvider : <strong>FeatureProvider</strong>
{
    public override void SetFeatures(IFeatureDefinitionContext context)
    {
        var sampleBooleanFeature = context.<strong>Create</strong>(&#x22;SampleBooleanFeature&#x22;, defaultValue: &#x22;false&#x22;);
        sampleBooleanFeature.<strong>CreateChildFeature</strong>(&#x22;SampleNumericFeature&#x22;, defaultValue: &#x22;10&#x22;);
        context.<strong>Create</strong>(&#x22;SampleSelectionFeature&#x22;, defaultValue: &#x22;B&#x22;);
    }
}</pre>
			<p>After creating a feature provider, we should register it in our module's
				<a href="/Pages/Documents/Module-System#DocModulePreInit">PreInitialize</a> 
	method as shown below:</p>
			<pre lang="cs"><strong>Configuration.Features</strong>.Providers.Add&#x3C;AppFeatureProvider&#x3E;();</pre>
			<h4>Basic Feature Properties</h4>
			<p>A feature definition requires two properties at least:</p>
			<ul>
				<li>
					<strong>Name</strong>: A unique name (as string) to identify the 
		feature.</li>
				<li>
					<strong>Default value</strong>: A default value. This is used when 
		we need the value of the feature and it's not available for current 
		tenant.</li>
			</ul>
			<p>Here, we defined a boolean feature named "SampleBooleanFeature" which's 
	default value is "false" (not enabled). We also defined two value features 
	(SampleNumericFeature is defined as child of SampleBooleanFeature).</p>
			<p>Tip: Create a const string for a feature name and use it everywhere to 
	prevent typing errors.</p>
			<h4>Other Feature Properties</h4>
			<p>While unique name and default value properties are required, there are some 
			optional properties for a detailed 
	control.</p>
			<ul>
				<li>
					<strong>Scope</strong>: A value in FeatureScopes enum. It can be
					<strong>Edition</strong> (if this feature can be set only for edition 
		level), <strong>Tenant</strong> (if this feature can be set only for 
		tenant level) or <strong>All</strong> (if this feature can be set for 
		editions and tenants, where tenant setting overrides it's edition's 
		setting). Default value is <strong>All</strong>.</li>
				<li>
					<strong>DisplayName</strong>: A localizable string to show the 
		feature's name to users.</li>
				<li>
					<strong>Description</strong>: A localizable string to show the 
		feature's detailed description to users.</li>
				<li>
					<strong>InputType</strong>: A UI input type for the feature. This 
		can be defined, then can be used while creating an automatic feature 
		screen.</li>
				<li>
					<strong>Attributes</strong>: An arbitrary custom dictionary of 
		key-value pairs those can be related to the feature.</li>
			</ul>
			<p>Let's see more detailed definitions for the features above:</p>
			<pre lang="cs">public class AppFeatureProvider : <strong>FeatureProvider</strong>
{
    public override void SetFeatures(IFeatureDefinitionContext context)
    {
        var sampleBooleanFeature = context.Create(
            AppFeatures.SampleBooleanFeature,
            defaultValue: &#x22;false&#x22;,
            displayName: L(&#x22;Sample boolean feature&#x22;),
            inputType: new CheckboxInputType()
            );

        sampleBooleanFeature.CreateChildFeature(
            AppFeatures.SampleNumericFeature,
            defaultValue: &#x22;10&#x22;,
            displayName: L(&#x22;Sample numeric feature&#x22;),
            inputType: new SingleLineStringInputType(new NumericValueValidator(1, 1000000))
            );

        context.Create(
            AppFeatures.SampleSelectionFeature,
            defaultValue: &#x22;B&#x22;,
            displayName: L(&#x22;Sample selection feature&#x22;),
            inputType: new ComboboxInputType(
                new StaticLocalizableComboboxItemSource(
                    new LocalizableComboboxItem(&#x22;A&#x22;, L(&#x22;Selection A&#x22;)),
                    new LocalizableComboboxItem(&#x22;B&#x22;, L(&#x22;Selection B&#x22;)),
                    new LocalizableComboboxItem(&#x22;C&#x22;, L(&#x22;Selection C&#x22;))
                    )
                )
            );
    }

    private static ILocalizableString L(string name)
    {
        return new LocalizableString(name, AbpZeroTemplateConsts.LocalizationSourceName);
    }
}</pre>
			<p>Note that: Input type definitions are not used by ASP.NET 
			Boilerplate. They can be used by applications while creating inputs 
			for features. ASP.NET Boilerplate just provides infrastructure to make it easier.</p>
			<h4>Feature Hierarchy</h4>
			<p>As shown in the sample feature providers, a feature can have <strong>
	child features</strong>. A Parent feature is generally defined as <strong>
	boolean</strong> feature. Child features will be available only if the 
	parent enabled. ASP.NET Boilerplate <strong>does not</strong> enforces but suggests this. Applications should 
	take care of it.</p>
			<h3>Checking Features</h3>
			<p>We define a feature to check it's value in the application to allow or 
	block some application features per tenant. There are different ways of 
	checking it.</p>
			<h4>Using RequiresFeature Attribute</h4>
			<p>We can use <strong>RequiredFeature</strong> attribute for a method or a 
	class as shown below:</p>
			<pre><strong>[RequiresFeature(&#x22;ExportToExcel&#x22;)]</strong>
public async Task&#x3C;FileDto&#x3E; GetReportToExcel(...)
{
    ...
}</pre>
			<p>This method is executed only if "ExportToExcel" feature is enabled for 
	the <strong>current tenant</strong> (current tenant is obtained from
				<a href="/Pages/Documents/Abp-Session">IAbpSession</a>). If it's not enabled, an 
				<strong>AbpAuthorizationException</strong> is thrown 
	automatically.</p>
			<p>Surely, RequiresFeature attribute should be used for <strong>boolean type 
	features</strong>. Otherwise, you may get exceptions.</p>

			<h5>RequiresFeature attribute notes</h5>
			<p>ASP.NET Boilerplate uses power of dynamic method interception for 
	feature checking. So, there is some restrictions for the methods use 
	RequiresFeature 
	attribute.</p>
			<ul>
				<li>Can not use it for private methods.</li>
				<li>Can not use it for static methods.</li>
				<li>Can not use it for methods of a non-injected class (We must use
					<a href="/Pages/Documents/Dependency-Injection">dependency injection</a>).</li>
			</ul>
			<p>Also,</p>
			<ul>
				<li>Can use it for any <strong>public</strong> method if the method is called over an 
					<strong>interface </strong>(like Application Services used over interface).</li>
				<li>A method should be <strong>virtual</strong> if it's called directly 
		from class reference (like ASP.NET MVC or Web API Controllers).</li>
				<li>A method should be <strong>virtual</strong> if it's <strong>protected</strong>.</li>
			</ul>

			<h4>Using IFeatureChecker</h4>
			<p>We can inject and use IFeatureChecker to check a feature manually (it's 
	automatically injected and directly usable for application services, MVC and 
	Web API controllers).</p>
			<h5>IsEnabled</h5>
			<p>Used to simply check if given feature is enabled or not. Example:</p>
			<pre lang="cs">public async Task&#x3C;FileDto&#x3E; GetReportToExcel(...)
{
    if (<strong>await FeatureChecker.IsEnabledAsync(&#x22;ExportToExcel&#x22;)</strong>)
    {
        throw new AbpAuthorizationException(&#x22;You don&#x27;t have this feature: ExportToExcel&#x22;);
    }

    ...
}</pre>
			<p>IsEnabledAsync and other methods have also sync versions.</p>
			<p>Surely, IsEnabled 
	method should be used for <strong>boolean type 
	features</strong>. Otherwise, you may get exceptions.</p>
			<p>If you just want to check a feature and throw exception as shown in the 
	example, you can just use <strong>CheckEnabled</strong> method.</p>
			<h5>GetValue</h5>
			<p>Used to get current value of a feature for value type features. Example:</p>
			<pre lang="cs">var createdTaskCountInThisMonth = GetCreatedTaskCountInThisMonth();
if (createdTaskCountInThisMonth &#x3E;= <strong>FeatureChecker.GetValue(&#x22;MaxTaskCreationLimitPerMonth&#x22;).To&#x3C;int&#x3E;()</strong>)
{
    throw new AbpAuthorizationException(&#x22;You exceed task creation limit for this month, sorry :(&#x22;);
}</pre>
			<p>FeatureChecker methods have also overrides to work features for a 
	specified tenantId, not only for the current tenantId.</p>
			<h4>Client Side</h4>
			<p>In the client side (javascript), we can use <strong>abp.features</strong> namespace to 
	get current values of the features.</p>
			<h5>isEnabled</h5>
			<pre lang="js">var isEnabled = abp.features.isEnabled('SampleBooleanFeature');</pre>
			<h5>getValue</h5>
			<pre lang="js">var value = abp.features.getValue('SampleNumericFeature');</pre>
			<h3>Feature Manager</h3>
			<p>If you need to definitions of features, you can inject and use <strong>
	IFeatureManager</strong>.</p>
			<h3>A Note For Editions</h3>
			<p>ASP.NET Boilerplate framework has not a built-in edition system because such 
	a system requires a database (to store editions, edition features, 
	tenant-edition mappings and so on...). Therefore, edition system is 
	implemented in <a href="/Pages/Documents/Zero/Edition-Management">module 
	zero</a>. You can use it to easily have an edition system, or you can 
	implement all yourself.</p>

		</div>

	</body>

</html>
